# -*- encoding: utf-8 -*-
# mumu:    1280x720
# windows: 1288x760
# 160每个Tab （7.4个Tab）
# Teressert OCR: 5.3.0 ver2022.12 (python-opencv)
#

#
# 每个手机号：每天最多注册两个新账号
# 真人验证是每个身份证每天最多验证1个
# 手机号也不是关键，应该还是控制啦终端IP的频率的
#
import os
import sys
import json
import time
import random
import pickle
import datetime
import requests
import traceback
import subprocess
#import pandas as pd
import pyautogui as pag
import easytrader as et #pywin32
import pygetwindow as gw

gX = {}     # global variable dict for everything
gAll = {}   # to make log easier and faster

def offWindows():
    try:
        # 使用 subprocess 模块调用 shutdown 命令重启 Windows
        subprocess.run(["shutdown", "/s", "/f", "/t", "120"], check=True)
        logOne("Windows 正在重启...")
    except subprocess.CalledProcessError as e:
        logOne(f"重启 Windows 时出错: {e}")
        sys.exit(1)
    finally:
        pass

"""
log to file
"""

#
# 棋盘 Topology
# Z1左一，Y1右1，ZL左雷，ZQ左旗，YL右雷，YQ右旗
# S1上一，SL上雷，SQ上旗，GB工兵（中一）
#

Y1X = 815
Y1Y = 540
Y2X = Y1X
Y2Y = Y1Y + 38
Y3X = Y1X
Y3Y = Y1Y + 38 + 38
Y4X = Y1X
Y4Y = Y1Y + 38 + 38 + 38
Y5X = Y1X
Y5Y = Y1Y + 38 + 38 + 38 + 38

# 总是主动的
# 工兵 (要去夺旗)
GBX = Y1X - 80 - 80
GBY = Y1Y

# 总是被攻击
SQX = 110
SQY = 345
SLX = SQX + 64
SLY = SQY

# 总是被攻击
# 大棋司令(不动，等着别人送死)
DQX = 880
DQY = 490

# Player 逆时针排序，准备时尽量奇数分身为对家，避免出问题
MMX = 110
MMY = 20

# 自己
MEX = 268
MEY = 20
# 右家
YJX = 438
YJY = 20
# 对家
DJX = 608
DJY = 20
# 左家
ZJX = 778
ZJY = 20

def ABSX(x):
    return x + gX["mmLeft"]

def ABSY(x):
    return x + gX["mmTop"]

def logOne(strOut, toConsole = False):
    line = '%s' % datetime.datetime.now().strftime('%m-%d %H:%M:%S')
    line = '%s %d %s' % (line, time.time(),  strOut)
    if toConsole:
        print(line)
    for i in range(10):
        done = False
        with open(gX["fileLog"],  'a', encoding = 'utf-8') as frd:
            frd.write(line + '\n')
            done = True
        if (done):
            break
        time.sleep(0.001 * (10 + 100 * random.random()))

def DING_MSG(msg, token = "f1581cc603b174720084c2aa1e0459cb0467ec8d398d8fcbdcc7940edfa0d3f6", at = True):
    """ send dingding warning """
    msg += " Lewayan @ "    # keyword for Ding
    msg += os.popen("hostname").read().strip()
    headers = {'content-type': 'application/json'}
    robot_url = "https://oapi.dingtalk.com/robot/send?access_token=%s" % token
    data = {}
    data["msgtype"] = "text"
    data["text"] = {"content": msg}
    if at:
        data["at"] = {"atMobiles" : [13439718200]}
    retry_times = 3
    for i in range(retry_times):
        try:
            r = requests.post(
                url=robot_url,
                data=json.dumps(data),
                headers=headers)
            if r.status_code == 200:
                break
        except Exception as e:
            logOne(repr(e), True)
        finally:
            pass
    return

"""
Time/date related stuff
"""
def HMS():
    return time.strftime("%H%M%S",  time.localtime())

def HM():
    return time.strftime("%H%M",  time.localtime())
    
def YMD():
    return time.strftime("%Y%m%d",  time.localtime())

def runDebug(cnt):
    #
    # file can't be removed in function with 'with', so remove it explictly after calling
    #
    if os.path.exists(gX["fileCmdGx"]):
        logOne(str(gX))
        os.remove(gX["fileCmdGx"])

def updateData(cnt):
    if (15 <= (gX["chuji"] % 20)) and ((gX["chuji"] % 20) <= 19):
        # 控制胜率，避免长期100%胜率
        logOne("to runPing", True)
        runPing(cnt)
    else:
        if (cnt % 4 == 2):
            # 没4轮必走一次，避免长期不走棋
            logOne("to runPing", True)
            runPing(cnt)
        else:
            logOne("to runShua", True)
            runShua(cnt)

def runShua(cnt):
    sec = 2
    gX["justFailed"] = False

    pag.click(ABSX(MEX), ABSY(MEY))
    time.sleep(sec) # 有时间刷新
    yingMove()

    pag.click(ABSX(ZJX), ABSY(ZJY))
    time.sleep(sec) # 有时间刷新
    shuMove(ZJX)
    if gX["justFailed"]:
        return

    pag.click(ABSX(DJX), ABSY(DJY))
    time.sleep(sec) # 有时间刷新
    yingMove()
   
    pag.click(ABSX(YJX), ABSY(YJY))
    time.sleep(sec) # 有时间刷新
    shuMove(ZJX)
    if gX["justFailed"]:
        return

    logOne("SHUA chuiji=%d" % (gX["chuji"]), True)
    if (gX["lastChuji"] != gX["chuji"]):
        gX["lastChuji"] = gX["chuji"]
        DING_MSG("SHUA chuiji=%d" % (gX["chuji"]))
    return

def runPing(cnt):
    sec = 2
    gX["justFailed"] = False

    pag.click(ABSX(MEX), ABSY(MEY))
    time.sleep(sec) # 有时间刷新
    yingMove()

    pag.click(ABSX(ZJX), ABSY(ZJY))
    time.sleep(sec) # 有时间刷新
    yingMove()
    if gX["justFailed"]:
        return
    if gX["round"] >= 3:
        # surrender
        point = locateSnip("tohe.png")
        if not (point is None):
            pag.click(point.x,  point.y)
            time.sleep(1)
            point = locateSnip("qiuhe.png")
            if not (point is None):
                pag.click(point.x,  point.y)
                time.sleep(1)
                point = locateSnip("queding.png")
                if not (point is None):
                    pag.click(point.x,  point.y)

    pag.click(ABSX(DJX), ABSY(DJY))
    time.sleep(sec) # 有时间刷新
    yingMove()
   
    pag.click(ABSX(YJX), ABSY(YJY))
    time.sleep(sec) # 有时间刷新
    yingMove()
    if gX["justFailed"]:
        return
    if gX["round"] >= 3:
        # surrender
        point = locateSnip("tohe.png")
        if not (point is None):
            pag.click(point.x,  point.y)
            time.sleep(1)
            point = locateSnip("qiuhe.png")
            if not (point is None):
                pag.click(point.x,  point.y)
                time.sleep(1)
                point = locateSnip("queding.png")
                if not (point is None):
                    pag.click(point.x,  point.y)

    gX["round"] += 1
    logOne("PING chuiji=%d" % (gX["chuji"]), True)
    if (gX["lastChuji"] != gX["chuji"]):
        gX["lastChuji"] = gX["chuji"]
        DING_MSG("PING chuiji=%d" % (gX["chuji"]))
    return

def closeOne(cnt):
    # Try first method
    win = gw.getWindowsWithTitle("MuMu模拟器")[0]
    win.close()
    # Try second method
    pag.click(gX["mmLeft"] + gX["mmWidth"] - 8, gX["mmTop"] + 8)
    time.sleep(1)
    # Try it again
    pag.click(gX["mmLeft"] + gX["mmWidth"] - 8, gX["mmTop"] + 8)
    time.sleep(1)

def locateSnip(vtxx):
    try:
        point = None
        point = pag.locateCenterOnScreen(gX["dir"] + "/snip/" + vtxx, confidence=0.9)
    except Exception as e:
        point = None
    finally:
        return point

def waitToShow(png, sec):
    stt = int(time.time())
    point = None
    while (point is None) and ((stt + sec) > time.time()):
        try:
            point = locateSnip(png)
        except Exception as e:
            point = None
        finally:
            if point is None:
                time.sleep(2)
            else:
                return True
    return False

def clickAgain(xx):
    point = locateSnip("again.png")
    if not (point is None):
        pag.click(point.x,  point.y)
        time.sleep(3)
    else:
        logOne("again not found for " + str(xx), True)

    sec = 3
    if xx == ZJX:
        pag.click(ABSX(YJX), ABSY(YJY))
    else:
        pag.click(ABSX(ZJX), ABSY(ZJY))
    time.sleep(2)
    point = locateSnip("again.png")
    if not (point is None):
        pag.click(point.x,  point.y)
        time.sleep(3)
    else:
        logOne("again not found for " + str(xx), True)

    pag.click(ABSX(MEX), ABSY(MEY))
    time.sleep(sec) # 有时间刷新
    point = locateSnip("again.png")
    if not (point is None):
        pag.click(point.x,  point.y)
        time.sleep(3)
    else:
        logOne("again not found for " + str(xx), True)

    pag.click(ABSX(DJX), ABSY(DJY))
    time.sleep(sec) # 有时间刷新
    point = locateSnip("again.png")
    if not (point is None):
        pag.click(point.x,  point.y)
        time.sleep(3)
    else:
        logOne("again not found for " + str(xx), True)

    gX["justFailed"] = True


# 不动直到自爆
def shuMove(xx):
    point = None
    point = locateSnip("queding.png")
    if not (point is None):
        pag.click(point.x,  point.y)

    point = None
    logOne("in shuMove", True)
    point = locateSnip("again.png")
    if not (point is None):
        pag.click(point.x,  point.y)
        time.sleep(1)
        return
    point = locateSnip("start.png")
    if not (point is None):
        pag.click(point.x,  point.y)
        time.sleep(1)
        gX["chuji"] += 1
        gX["round"] = 0 # 重新统计
        return
    if (int(gX["chuji"] / 4) * 4) != gX["chuji"]:
        return

    point = locateSnip("hang.png")
    if not (point is None):
        return
    point = locateSnip("indui.png")
    if not (point is None):
        return
    point = locateSnip("indi.png")
    if not (point is None):
        if point.y < 200:
            return
    sec= 7
    point = locateSnip("todo.png")
    if not (point is None):
        sec = 5
    if waitToShow("wozou.png", sec):
        logOne("shu wozou", True)
        if (gX["tsShuMove"] + 3600) < time.time():
            gX["tsShuMove"] = time.time()
            gX["shuMoved"] = True
            pag.click(ABSX(Y4X), ABSY(Y4Y))
            pag.click(ABSX(Y4X) - 80, ABSY(Y4Y))    # 进营
            pag.click(ABSX(Y4X), ABSY(Y4Y))         # 出营
            time.sleep(5)
        elif gX["shuMoved"]:
            gX["shuMoved"] = False
            pag.click(ABSX(Y2X), ABSY(Y2Y))
            pag.click(ABSX(Y2X) - 80, ABSY(Y2Y))    # 进营
            pag.click(ABSX(Y2X), ABSY(Y2Y))         # 出营
            time.sleep(5)
        else:
            pag.click(ABSX(Y1X) - 80, ABSY(Y1Y)) # 确保不会乱选棋子
            time.sleep(15)
            pag.click(ABSX(Y3X) - 80, ABSY(Y3Y)) # 确保不会乱选棋子
            if waitToShow("duisi.png", 8):
                if waitToShow("failure.png", 30):
                    clickAgain(xx)

def yingMove():
    point = None
    point = locateSnip("queding.png")
    if not (point is None):
        pag.click(point.x,  point.y)

    point = None
    logOne("in yingMove", True)
    point = locateSnip("antiad.png")
    if not (point is None):
        pag.click(point.x,  point.y + 19)
        time.sleep(1)
    point = locateSnip("again.png")
    if not (point is None):
        logOne("ying again", True)
        pag.click(point.x,  point.y)
        time.sleep(1)
        return
    point = locateSnip("start.png")
    if not (point is None):
        pag.click(point.x,  point.y)
        time.sleep(1)
        gX["chuji"] += 1
        gX["round"] = 0 # 重新统计
        return
    if (int(gX["chuji"] / 4) * 4) != gX["chuji"]:
        return

    point = locateSnip("hang.png")
    if not (point is None):
        return
    point = locateSnip("indui.png")
    if not (point is None):
        return
    point = locateSnip("indi.png")
    if not (point is None):
        if point.y < (200 + gX["mmTop"]):
            return
    sec= 7
    point = locateSnip("todo.png")
    if not (point is None):
        sec = 5
    if waitToShow("wozou.png", sec):
        logOne("ying wozou", True)
        pag.click(ABSX(Y1X) - 80, ABSY(Y1Y)) # 确保不会乱选棋子
        if locateSnip("hr55.png") and False: # force failure case
            # 找到了hr55，就开始挖雷夺旗
            logOne(str(point.x) + " hr55 " + str(point.y), True)          
            pag.click(ABSX(GBX), ABSY(GBY))
            pag.click(ABSX(SLX), ABSY(SLY)) # 挖雷
            pag.click(ABSX(Y1X) - 80, ABSY(Y1Y)) # 确保不会乱选棋子
            pag.click(ABSX(SLX), ABSY(SLY))
            pag.click(ABSX(SQX), ABSY(SQY)) # 夺旗
        else:
            # 没找到hr55，就进出营，等对方送死，避免总是走一个棋子，反反作弊
            if random.random() > 0.5:
                pag.click(ABSX(Y2X), ABSY(Y2Y))
                pag.click(ABSX(Y2X) - 80, ABSY(Y2Y))    # 进营
                pag.click(ABSX(Y2X), ABSY(Y2Y))         # 出营
            else:
                pag.click(ABSX(Y4X), ABSY(Y4Y))
                pag.click(ABSX(Y4X) - 80, ABSY(Y4Y))    # 进营
                pag.click(ABSX(Y4X), ABSY(Y4Y))         # 出营
            time.sleep(3)

def setArgv():
    gX["runThs"] = True
    gX["daemon"] = True
    for i in range(len(sys.argv)):
        if "-runths=" in sys.argv[i]:
            cells = sys.argv[i].split("=")
            if cells[1].lower() == "false":    
                gX["runThs"] = False
        if "-daemon=" in sys.argv[i]:
            cells = sys.argv[i].split("=")
            if cells[1].lower() == "false":    
                gX["daemon"] = False

def initMe():
    thisDirPath, thisFileName = os.path.split(__file__)
    thisDirPath = os.path.abspath(thisDirPath)
    gX["fileLog"] = thisDirPath + "/shua.log"
    setArgv()   ### setArgv
    gX["dir"] = thisDirPath
    gX["file"] = thisFileName
    gX["bootYmd"] = YMD()
    gX["bootHms"] = HMS()
    gX["fileCmdGx"] = thisDirPath + "/gx.cmd"
    gX["fileCmdAbort"] = thisDirPath + "/abort.cmd"
    gX["tsLast"] = time.time()
    pag.FAILSAFE = False
    gX["tsShuMove"] = time.time()
    gX["shuMoved"] = False
    # >>> global variables
    logOne("now exited FUNC %s" % sys._getframe().f_code.co_name, True)

    # 通过窗口标题查找窗口
    win = gw.getWindowsWithTitle("MuMu模拟器")[0]
    gX["mmLeft"] = win.left
    gX["mmTop"] = win.top
    gX["mmWidth"] = win.width
    gX["chuji"] = 0
    gX["lastChuji"] = 0
    gX["round"] = 0
    logOne("mmLeft=" + str(win.left), True)
    logOne("mmTop=" + str(win.top), True)
    logOne("mmWidth=" + str(win.width), True)
    win.activate()
    
def runMe():
    if (gX["mmLeft"] < -20) or (gX["mmTop"] < -20):
        logOne("MuMu is not found", True)
        return
    pag.click(ABSX(MMX), ABSY(MMY))
    cnt = 0
    tsStart = int(time.time())
    logOne("start now %d" % tsStart, True)
    while True:
        try:
            if ((time.time() - tsStart) > (3600 * 1)) or (gX["chuji"] > 60):
                logOne("finish now - time limit", True)
                closeOne(cnt)
                # offWindows()
                break
            runDebug(cnt)
            updateData(cnt)
        except Exception as e:
            logOne(repr(e), True)
            logOne(traceback.format_exc())
            logOne(gX)
            time.sleep(9)
            initMe()
            cnt = 0
        finally:
            pass
        if gX["daemon"] == False:
            break
        if os.path.exists(gX["fileCmdAbort"]):
            os.remove(gX["fileCmdAbort"])
            closeOne(cnt)
            break
        cnt += 1
        time.sleep(1)
        if ("061500" < HMS()) and (HMS() < "062515"):
            logOne("game ban duration", True)
            # closeOne(cnt)
            # ioffWindows()
            # break

if __name__ == "__main__":
    print("booting")
    gX["chuji"] = 0
    try:
        initMe()
        runMe()
    except Exception as e:
        logOne(repr(e))
        logOne(traceback.format_exc())
        logOne(gX)
    finally:
        pass
    print("exited")
